package net.xdclass.aspect;

import lombok.extern.slf4j.Slf4j;
import net.xdclass.annotation.RepeatSubmit;
import net.xdclass.constant.RedisKey;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.execption.BizException;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.model.LoginUser;
import net.xdclass.util.CommonUtil;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 定义切面
 */
@Slf4j
@Component
@Aspect
public class RepeatSubmitAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * 定义 @Pointcut注解表达式，
     * 方式一：@annotation：当执行的方法上拥有指定的注解时生效（我们采用这）
     * 方式二：execution：一般用于指定方法的执行
     *
     * @param repeatSubmit
     */
    @Pointcut("@annotation(repeatSubmit)")
    public void pointcutNoRepeatSubmit(RepeatSubmit repeatSubmit) {

    }

    /**
     * * 环绕通知, 围绕着方法执行
     * * @Around 可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
     * *
     * * 方式一：单用 @Around("execution(* net.xdclass.controller.*.*(..))")可以
     * * 方式二：用@Pointcut和@Around联合注解也可以（我们采用这个）
     * *
     * *
     * * 两种方式
     * * 方式一：加锁 固定时间内不能重复提交
     * * <p>
     * * 方式二：先请求获取token，这边再删除token,删除成功则是第一次提交
     *
     * @param joinPoint
     * @param repeatSubmit
     * @return
     * @throws Throwable
     */
    @Around("pointcutNoRepeatSubmit(repeatSubmit)")
    public Object around(ProceedingJoinPoint joinPoint, RepeatSubmit repeatSubmit) throws Throwable {
        Boolean res = false;
        //获取servlet
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        //放重提交类型
        String type = repeatSubmit.limitType().name();

        if (type.equals(RepeatSubmit.Type.PARAM.name())) {
            //方式一方法参数
            long lockTime = repeatSubmit.lockTime();

            String ip = CommonUtil.getIpAddr(request);
            //获取注解
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            //目标类、方法
            String className = method.getDeclaringClass().getName();
            String name = method.getName();
            String key = String.format("%s#%s#%s#%s",loginUser.getAccountNo(),ip, className, name);

            log.info("key={}", key);
            // 分布式锁 TODO, 直接设置key配置过期时间也行
            res = redisTemplate.opsForValue().setIfAbsent(key, "1", lockTime, TimeUnit.SECONDS);

        } else {
            //方式二  token
            String requestToken = request.getHeader("request-token");
            if (StringUtils.isBlank(requestToken)) {
                throw new BizException(BizCodeEnum.ORDER_CONFIRM_TOKEN_EQUAL_FAIL);
            }

            String key = String.format(RedisKey.SUBMIT_ORDER_TOKEN_KEY, loginUser.getAccountNo(), requestToken);
            /**
             * 提交表单的token key
             */
            res = redisTemplate.delete(key);
        }

        if (!res) {
            throw new BizException(BizCodeEnum.ORDER_CONFIRM_REPEAT);
        }

        System.out.println("目标方法执行前");
        Object object = joinPoint.proceed();
        System.out.println("目标方法执行后");
        return object;
    }


}
